#ifndef CPPTL_JSON_READER_H_INCLUDED
# define CPPTL_JSON_READER_H_INCLUDED

# include "features.h"
# include "value.h"
# include <deque>
# include <stack>
# include <string>
# include <iostream>

namespace Json
{

    /** \brief Unserialize a <a HREF="http://www.json.org">JSON</a> document into a Value.
     *
     */
    class JSON_API Reader
    {
    public:
        typedef char Char;
        typedef const Char *Location;

        /** \brief Constructs a Reader allowing all features
         * for parsing.
         */
        Reader();

        /** \brief Constructs a Reader allowing the specified feature set
         * for parsing.
         */
        Reader( const Features &features );

        /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
         * \param document UTF-8 encoded string containing the document to read.
         * \param root [out] Contains the root value of the document if it was
         *             successfully parsed.
         * \param collectComments \c true to collect comment and allow writing them back during
         *                        serialization, \c false to discard comments.
         *                        This parameter is ignored if Features::allowComments_
         *                        is \c false.
         * \return \c true if the document was successfully parsed, \c false if an error occurred.
         */
        bool parse( const std::string &document,
                    Value &root,
                    bool collectComments = true );

        /** \brief Read a Value from a <a HREF="http://www.json.org">JSON</a> document.
         * \param document UTF-8 encoded string containing the document to read.
         * \param root [out] Contains the root value of the document if it was
         *             successfully parsed.
         * \param collectComments \c true to collect comment and allow writing them back during
         *                        serialization, \c false to discard comments.
         *                        This parameter is ignored if Features::allowComments_
         *                        is \c false.
         * \return \c true if the document was successfully parsed, \c false if an error occurred.
         */
        bool parse( const char *beginDoc, const char *endDoc,
                    Value &root,
                    bool collectComments = true );

        /// \brief Parse from input stream.
        /// \see Json::operator>>(std::istream&, Json::Value&).
        bool parse( std::istream &is,
                    Value &root,
                    bool collectComments = true );

        /** \brief Returns a user friendly string that list errors in the parsed document.
         * \return Formatted error message with the list of errors with their location in
         *         the parsed document. An empty string is returned if no error occurred
         *         during parsing.
         */
        std::string getFormatedErrorMessages() const;

    private:
        enum TokenType
        {
            tokenEndOfStream = 0,
            tokenObjectBegin,
            tokenObjectEnd,
            tokenArrayBegin,
            tokenArrayEnd,
            tokenString,
            tokenNumber,
            tokenTrue,
            tokenFalse,
            tokenNull,
            tokenArraySeparator,
            tokenMemberSeparator,
            tokenComment,
            tokenError
        };

        class Token
        {
        public:
            TokenType type_;
            Location start_;
            Location end_;
        };

        class ErrorInfo
        {
        public:
            Token token_;
            std::string message_;
            Location extra_;
        };

        typedef std::deque<ErrorInfo> Errors;

        bool expectToken( TokenType type, Token &token, const char *message );
        bool readToken( Token &token );
        void skipSpaces();
        bool match( Location pattern,
                    int patternLength );
        bool readComment();
        bool readCStyleComment();
        bool readCppStyleComment();
        bool readString();
        void readNumber();
        bool readValue();
        bool readObject( Token &token );
        bool readArray( Token &token );
        bool decodeNumber( Token &token );
        bool decodeString( Token &token );
        bool decodeString( Token &token, std::string &decoded );
        bool decodeDouble( Token &token );
        bool decodeUnicodeCodePoint( Token &token,
                                     Location &current,
                                     Location end,
                                     unsigned int &unicode );
        bool decodeUnicodeEscapeSequence( Token &token,
                                          Location &current,
                                          Location end,
                                          unsigned int &unicode );
        bool addError( const std::string &message,
                       Token &token,
                       Location extra = 0 );
        bool recoverFromError( TokenType skipUntilToken );
        bool addErrorAndRecover( const std::string &message,
                                 Token &token,
                                 TokenType skipUntilToken );
        void skipUntilSpace();
        Value &currentValue();
        Char getNextChar();
        void getLocationLineAndColumn( Location location,
                                       int &line,
                                       int &column ) const;
        std::string getLocationLineAndColumn( Location location ) const;
        void addComment( Location begin,
                         Location end,
                         CommentPlacement placement );
        void skipCommentTokens( Token &token );

        typedef std::stack<Value *> Nodes;
        Nodes nodes_;
        Errors errors_;
        std::string document_;
        Location begin_;
        Location end_;
        Location current_;
        Location lastValueEnd_;
        Value *lastValue_;
        std::string commentsBefore_;
        Features features_;
        bool collectComments_;
    };

    /** \brief Read from 'sin' into 'root'.

     Always keep comments from the input JSON.

     This can be used to read a file into a particular sub-object.
     For example:
     \code
     Json::Value root;
     cin >> root["dir"]["file"];
     cout << root;
     \endcode
     Result:
     \verbatim
     {
    "dir": {
        "file": {
    	// The input stream JSON would be nested here.
        }
    }
     }
     \endverbatim
     \throw std::exception on parse error.
     \see Json::operator<<()
    */
    std::istream& operator>>( std::istream&, Value& );

} // namespace Json

#endif // CPPTL_JSON_READER_H_INCLUDED
